//===-- InterfaceConformanceAnalysis.cpp - Interface Conformance Analysis ---===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
// This analysis collects a set of nominal types (classes, structs, and enums)
// that conform to a protocol during whole module compilation. We only track
// protocols that are non-public.

#include "polarphp/pil/optimizer/analysis/InterfaceConformanceAnalysis.h"
#include "polarphp/ast/AstContext.h"
#include "polarphp/ast/AstWalker.h"
#include "polarphp/ast/Module.h"
#include "polarphp/ast/InterfaceConformance.h"
#include "polarphp/pil/lang/PILInstruction.h"
#include "polarphp/pil/lang/PILModule.h"
#include "polarphp/pil/lang/PILValue.h"

using namespace polar;

namespace {
/// A helper class to collect all nominal type declarations.
class NominalTypeWalker : public AstWalker {
   InterfaceConformanceAnalysis::InterfaceConformanceMap &InterfaceConformanceCache;

public:
   NominalTypeWalker(InterfaceConformanceAnalysis::InterfaceConformanceMap
                     &InterfaceConformanceCache)
      : InterfaceConformanceCache(InterfaceConformanceCache) {}

   bool walkToDeclPre(Decl *D) override {
      /// (1) Walk over all NominalTypeDecls to determine conformances.
      if (auto *NTD = dyn_cast<NominalTypeDecl>(D)) {
         auto Interfaces = NTD->getAllInterfaces();
         for (auto &Interface : Interfaces) {
            if (Interface->getEffectiveAccess() <= AccessLevel::Internal) {
               InterfaceConformanceCache[Interface].push_back(NTD);
            }
         }
      }
      /// (2) Walk over all ExtensionDecls to determine conformances.
      if (auto *e = dyn_cast<ExtensionDecl>(D)) {
         auto *ntd = e->getExtendedNominal();
         if (!isa<InterfaceDecl>(ntd)) {
            for (auto *conformance : e->getLocalConformances()) {
               if (isa<NormalInterfaceConformance>(conformance)) {
                  auto *proto = conformance->getInterface();
                  if (proto->getEffectiveAccess() <= AccessLevel::Internal) {
                     InterfaceConformanceCache[proto].push_back(ntd);
                  }
               }
            }
         }
      }
      return true;
   }
};
} // end anonymous namespace

void InterfaceConformanceAnalysis::init() {

   // We only do this in Whole-Module compilation mode.
   if (!M->isWholeModule())
      return;

   // Process all types implementing protocols.
   SmallVector<Decl *, 32> Decls;

   // Find all top level declarations.
   M->getTypePHPModule()->getTopLevelDecls(Decls);

   /// This operation is quadratic and should only be performed
   /// in whole module compilation!
   NominalTypeWalker Walker(InterfaceConformanceCache);
   for (auto *D : Decls) {
      D->walk(Walker);
   }
}

/// Recursively traverse the conformance lists to determine sole conforming
/// class, struct or enum type.
NominalTypeDecl *
InterfaceConformanceAnalysis::findSoleConformingType(InterfaceDecl *Interface) {

   /// First check in the SoleConformingTypeCache.
   auto SoleConformingTypeIt = SoleConformingTypeCache.find(Interface);
   if (SoleConformingTypeIt != SoleConformingTypeCache.end())
      return SoleConformingTypeIt->second;

   SmallVector<InterfaceDecl *, 8> PDWorkList;
   SmallPtrSet<InterfaceDecl *, 8> VisitedPDs;
   NominalTypeDecl *SoleConformingNTD = nullptr;
   PDWorkList.push_back(Interface);
   while (!PDWorkList.empty()) {
      auto *PD = PDWorkList.pop_back_val();
      // Interfaces must have internal or lower access.
      if (PD->getEffectiveAccess() > AccessLevel::Internal) {
         return nullptr;
      }
      VisitedPDs.insert(PD);
      auto NTDList = getConformances(PD);
      for (auto *ConformingNTD : NTDList) {
         // Recurse on protocol types.
         if (auto *Proto = dyn_cast<InterfaceDecl>(ConformingNTD)) {
            // Ignore visited protocol decls.
            if (!VisitedPDs.count(Proto))
               PDWorkList.push_back(Proto);
         } else { // Classes, Structs and Enums are added here.
            // Bail if more than one conforming types were found.
            if (SoleConformingNTD && ConformingNTD != SoleConformingNTD) {
               return nullptr;
            } else {
               SoleConformingNTD = ConformingNTD;
            }
         }
      }
   }

   // Bail if we did not find a sole conforming type.
   if (!SoleConformingNTD)
      return nullptr;

   // Generic declarations are ignored.
   if (SoleConformingNTD->isGenericContext()) {
      return nullptr;
   }

   // Populate SoleConformingTypeCache.
   SoleConformingTypeCache.insert(std::pair<InterfaceDecl *, NominalTypeDecl *>(
      Interface, SoleConformingNTD));

   // Return SoleConformingNTD.
   return SoleConformingNTD;
}

// Wrapper function to findSoleConformingType that checks for additional
// constraints for classes using ClassHierarchyAnalysis.
bool InterfaceConformanceAnalysis::getSoleConformingType(
   InterfaceDecl *Interface, ClassHierarchyAnalysis *CHA, CanType &ConcreteType) {
   // Determine the sole conforming type.
   auto *NTD = findSoleConformingType(Interface);
   if (!NTD)
      return false;

   // Sole conforming class should not be open access or have any derived class.
   ClassDecl *CD;
   if ((CD = dyn_cast<ClassDecl>(NTD)) &&
       (CD->getEffectiveAccess() == AccessLevel::Open ||
        CHA->hasKnownDirectSubclasses(CD))) {
      return false;
   }

   // Save the concrete type.
   ConcreteType = NTD->getDeclaredType()->getCanonicalType();
   return true;
}

InterfaceConformanceAnalysis::~InterfaceConformanceAnalysis() {}
